/* Copyright 2009 Palm, Inc.  All rights reserved. */

function AppAssistant(appController){
   // QDLogger.log( "AppAssistant");
    this.appController = appController;
    this.mockTelephony = new MockTelephony();
    // TODO: move this elsewhere?
	this.contacts = new Contact();
	this.announcer = new AnnouncerAssistant(this);
	this.telephonyEventListener = new TelephonyEventListener(this);
	this.sceneStack = [];
	this.lockSceneStack = [];
	// stores emergency numbers
	this.emergencyNumbers = ["911", "112", "000", "08", "110", "999", "118", "119", "#911", "*911"]; // default emergency numbers
	this.initialLaunch = true;
	this.hidden = false; // todo see [PIX-10371]
	
	// screen lock requires password or pin
	this.screenLockSecured = false;
	
	// lock screen on (and may require password or pin)
	this.screenLocked = false;
	
	// EAS Security Policy Status (Pending, Active or None)
	this.securityPolicyState = "none";
	
	this.suppressNextMissed = false;

	// default preferences	
	this.stickyDialpad = true;
	this.experimentalMode = false;
	this.puckMode = true;
	this.hideCallLogCallDuration = false;
	
	this.quickDial ={};
	this.registerPhoneprefsForQuickdial();
	
	this.onLockScreenActivate = this.onLockScreenActivate.bindAsEventListener(this);
	this.onLockScreenDeactivate = this.onLockScreenDeactivate.bindAsEventListener(this);
	this.unblockKeyboard = this.unblockKeyboard.bind(this);  
	
	this.emergencyModeExpirationMs = 0;
	this.phoneGSMNorthAmericanSettings = false;
	this.hideEmergencyNumbersFromCallLog = false;
	this.useCarrierVerificationMMSCURL = false;
	
    this.loadProperties();
	this.configureLocale();
	
	this.setupShutdown();
//*************Sam, patch start for IP dial************
	this.ipDialingPrefixRequest = new Mojo.Service.Request("palm://com.palm.systemservice", {
		method: 'getPreferences',
		parameters: {
			"subscribe":true,
			'keys': ['ipDialingPrefixNumber']
		},
		onSuccess: (function(response){
			if (response && response.ipDialingPrefixNumber) {
				this.ipDialingPrefixNumber = response.ipDialingPrefixNumber;
			}
		}).bind(this),
	});
//*************Sam, patch end for IP dial  ************
}

AppAssistant.prototype.stageName = "PhoneApp";
AppAssistant.prototype.sceneDialpad = "dialpad";
AppAssistant.prototype.sceneActiveCall = "activecall";
AppAssistant.prototype.sceneCallLog = "calllog";

AppAssistant.prototype.loadProperties = function() {
  var sourcesText = palmGetResource(Mojo.appPath + "properties.json", true);
  if (sourcesText) {
    AppAssistant.properties = Mojo.parseJSON(sourcesText);
  }
}

AppAssistant.prototype.handleLaunch = function(params){
    QDLogger.log( "handleLaunch");
	
	var args = params;
	if(args.number)
		args.number = (args.number).toLowerCase();
  
	// reset aborted active call
	this.telephonyEventListener.continueCall = true;
	
	if (args) {
		QDLogger.log( "handleLaunch", "args");
		
		// first use passes this parameter to change phoneapp UI
		// causes new background and some functions to be disabled
		if (PalmSystem.isMinimal || (args.firstuse && args.firstuse === true)) {
			this.firstuse = true;
		} else {
			this.firstuse = false;
		}
		
		// if this an otasp launch, note it; otherwise clear
		if (args.otasp === true) {
			this.otasp = true;
		} else {
			this.otasp = false;
		}
		
		//If the First Use passes the locale different than the current one, change the Mojo.Locale(Framework)
		//so that, the locale specific view files and strings table will be loaded.
		if(this.firstuse) {
			if(args.locale && args.locale.length === 5 && args.locale != Mojo.Locale.current) {
				Mojo.Locale.set(args.locale);
				//Re-load the Messages table
				Messages = new ParentMessages();
			}
		} 
		
		if (args.action) {
			QDLogger.log( "handleLaunch", "action: " , args.action);
			switch(args.action) {
				case "voicemail":
					var appControl = Mojo.Controller.getAppController();
					appControl.removeBanner('voicemail');
					this.launchVoicemail();
					return;
				case "emergency":
					this.launchEmergencyCall();
					return;
				case "missed":
					this.launchMissed();
					return;
				case "answer":
					//this.announcer.announceIncomingAnswer();
					return;
				case "reject":
					this.announcer.announceIncomingClose(AnnouncerAssistant.kIncomingRejected);
					return;
				case "activecall":
					this.showActiveCall();
					this.focusStage();
					return;
				case "ignored":
					if (!(args.contact.id)) {
						if (this.contacts.isNumberValid(args.number)) {
							TelephonyCommands.contactsLaunchWithPseudocard(args.number);
						}
					} else {
						TelephonyCommands.contactsLaunchWithId(args.contact.id);
					}
					return;
				case "pin":
					this.createLockStage();
					return;
				case "servicemessage":
					this.announcer.announceServiceMsgPopup(args.counter, args.message);
					return;
				case "disconnect":
					this.closePhoneStage();
					return;
				default:
					return;
			}
		} else if (args.prefill) { // insert number into dialpad but don't dial
			QDLogger.log( "handleLaunch", "prefill: " , args.prefill);
			this.showDialpad({"action": "fill", "number": TelephonyCommands.normalizeNumber(args.prefill)});
			this.focusStage();
		} else if (args.number) { // dial number
			QDLogger.log( "handleLaunch", "number: " , args.number , args.name  , args.id, args.personId);
			var contact = {}
			if (args.name) {
				contact.name = args.name
			} else if (args.id){
				contact.id = args.id
			} else if (args.personId) {
				contact.id = args.personId;
			}
			
			if (args.label) {
				contact.label = args.label;
			}
			if(args.number.length>=4 && args.number.length<=7 && this.getQuickdialPrefix(args.number) !== undefined)
				args.number = this.getQuickdialPrefix(args.number)+args.number
			this.launchDial(args.number, null, contact);
		} else if (args.target) { // 
			QDLogger.log( "handleLaunch", "target: " , args.target);
			this.launchURI(args.target);
		} else if(args.provisioning){
			this.showProvisioning(args.status);
		} else if (args.defaultData) {
			this.contacts.carrierBookSave(args.defaultData.carrierbook);
			return;
		} else if (args.hideCallDuration !== undefined) {
			QDLogger.log( "handleLaunch", "deprecated hide call duration mechanism; ignored");
			return;
		} else if (args.launchedAtBoot === true) {
			this.initialLaunch = false;
			this.createLockStage();
			this.loadSettings();
			return;
		} else {
			// when in first use, always default to doing nothing for invalid args; 
			// otherwise default to showing dialpad
			if(PalmSystem.isMinimal)
				return 
			else
				this.showDialpad();
		} 
    } else {
		
		if (!(this.telephonyEventListener.isPendingOrActive())) {
			if (this.initialLaunch === false) {
				QDLogger.log( "handleLaunch", "no args, no calls: focusing stage");
				var appControl = Mojo.Controller.getAppController();
    			var stageController = appControl.getStageController(this.stageName);
					
				if (!stageController) {
					this.showDialpad();
				}
				
				this.focusStage();
			} else {
				QDLogger.log( "handleLaunch", "initial launch, so doing nothing");
				this.initialLaunch = false;
				return;
			}
		} else {
			QDLogger.log( "handleLaunch", "no args, call exists: showing active");
			this.showActiveCall();
			this.focusStage();
		}
    }
};

// parse URI and dial number in it
AppAssistant.prototype.launchURI = function(uri){
	var offset = -1;
	if (uri.indexOf("tel:") == 0) {
		offset = 4;
	} else if (uri.indexOf("wtai:") == 0) {
		offset = 5;
	}
	
	if (offset !== -1) {
		var number = unescape(uri.substr(offset));
		this.showDialpad({"action": "fill", "number": TelephonyCommands.normalizeNumber(number)});		
	} else {
		this.showDialpad();
	};
	this.focusStage();
};

// if we have a vaild voicemail number, just dial it.  otherwise go to the dialpad
AppAssistant.prototype.launchVoicemail = function(){
	/* NOV-53509: we consider possession and use of an already paired device to be 
	 * authentication enough
	 
	if (this.isScreenLockedAndSecured()) {
		QDLogger.error( "AppAssistant#launchVoicemail", "attempted dial while locked");
		return;
	}
	*/
	this.blockKeyboard = true;
	this.unblockKeyboard.delay(1.25);

	if (this.telephonyEventListener.voicemailNumber
		&& this.telephonyEventListener.voicemailNumber.length > 0) {
		this.launchDial(this.telephonyEventListener.voicemailNumber);
	} else {
		this.focusStage();
		this.showDialpad({
			"action": "voicemail"
		});
	}
};

AppAssistant.prototype.launchMissed = function() {
	this.showCallLog("missed");
}



// prefills the dialpad with the first emergency number in the list, or if none defined
AppAssistant.prototype.launchEmergencyCall = function() {
	var number = PlatformType.getDefaultEmergencyNumber();
	this.focusStage();
	this.showDialpad({"action": "fill", "number": number});
}

// launches the phone app by dialing a number.
// if it's a 'simple' number, just parses and launches active call
// (simple = a number that will definitely be dialed--i.e. not MMI or ##)
// if it's not, then launches dialpad with number
AppAssistant.prototype.launchDial = function(number, calllogType, contact) {
	QDLogger.log( "AppAssistant#launchDial",number ," calllogType:",calllogType,":");
	
	//*************Sam, patch start for IP dial************
	if (number.match("^wp")) {
		number = number.slice ("wp".length);
		if (this.ipDialingPrefixNumber) {
			if (!number.match("^" + this.ipDialingPrefixNumber)) {
				number = this.ipDialingPrefixNumber + number;
			}
		}
	}
	//*************Sam, patch end for IP dial  ************

	/* NOV-53509: we consider possession and use of an already paired device to be 
	 * authentication enough.
	
	if (this.isScreenLockedAndSecured()) {
		QDLogger.error( "AppAssistant#launchDial", "attempted dial while locked");
		return;
	}
	*/
	
	if ( Object.isString(calllogType) ) {
		this.calllogType = calllogType;
	}
	
	// stringify
	number = String(number);
	
	// turn screen on
	TelephonyCommands.displayOn();
	
	number = TelephonyCommands.normalizeNumber(number);
	
	// MMI and pound-pound end with non-numerics, so if we find one,
	// send through dialpad
	var digits = '+0123456789';
	this.blockKeyboard = true;
	this.unblockKeyboard.delay(1.25);
	
	if (number === undefined
		|| number === null
		|| number.length < 1
		|| digits.indexOf(number.charAt(number.length-1)) === -1
		|| number.charAt(0) === '#'
		|| !this.telephonyEventListener.serviced) {
		this.focusStage();
		this.showDialpad({"action": "dial", "number": number});
	} else {	
		var result = DialStringParser.interpretDialString(number, PlatformType.isGSM(), this.dialFailure.bind(this), 
			this.launchDialMessage.bind(this), this.handleUnknownDialResult.bind(this));
	
		if (result.action === DialAction.DIAL) {
			if (this.telephonyEventListener.isPendingOrActive()) {
				this.focusStage();
			} else {
				this.focusOnActiveCallSceneCreation = true;
			}
		
			this.telephonyEventListener.conjureDialingMessage(number, result.pauseWaitDigits, contact);
		} else {
			QDLogger.log( "AppAssistant::launchDialMessage", result.action);
			this.focusStage();
		}
	}
};

AppAssistant.prototype.unblockKeyboard = function() {
	this.blockKeyboard = false;
}

// stub, in the unlikely event a non-dial string was passed to the launch dialer
// should never execute. 
AppAssistant.prototype.launchDialMessage = function(message) {
	QDLogger.log( "AppAssistant::launchDialMessage",message);
	this.focusStage();
};

// propagate dial failure
AppAssistant.prototype.dialFailure = function(result, number) {
	QDLogger.log( "AppAssistant#dialFailure: " , number);
	this.focusStage();
	this.showDialpad({
		action: "dialfail", 
		"number": number, 
		"result": result
	});
};

// stub, in the unlikely event a non-dial string was passed to the launch dialer.
// should never execute.
AppAssistant.prototype.handleUnknownDialResult = function(result) {
	QDLogger.log( "AppAssistant::handleUnknownDialResult: " , result);
};

// scene management and switching 
//
// since the phoneapp shows scenes based on radio events, and the framework
// asyncronously maintains its scene stack, the phoneapp must keep track of
// which scene is showing.  relying on the framework to determine which scene 
// is on top will cause it to go out of sync if a second radio event causing a second
// scene push arrives before the framework finishes the first.

// these function assume that this.sceneStack always has an accurate copy
// of the current scene stack.
// 
// also, they assume that if there's an active call, it's the bottom-most scene
// if there's a dialpad, it's the next bottom-most scene.
// other scenes stack on top of each other and always return to dialpad or active call

// get name of scene at the top of our internal scene stack
AppAssistant.prototype.topScene = function() {
	if (this.sceneStack.length > 0) {
		return this.sceneStack[this.sceneStack.length-1];
	} else {
		return '';
	}
};

AppAssistant.prototype.hasScene = function(name) {
	if (!(this.sceneStack)) {
		return false;
	} else {
		for (var i = 0; i < this.sceneStack.length; i++) {
			if (this.sceneStack[i] == name) {
				return true;
			}
		}
		return false;
	}
}

// creates the stage and executes callback on creation
// clears sceneStack
AppAssistant.prototype.createPhoneStage = function(callback) {
	this.sceneStack = [];
	var appControl = Mojo.Controller.getAppController();
	var f = function(sc) {
		//FIXME: enable for keepalive
		sc.window.Mojo.show = this.onShow.bind(this);
		sc.window.Mojo.hide = this.onHide.bind(this, sc);
		sc.document.addEventListener(Mojo.Event.stageActivate, function() {
			sc.useSceneTransitions(true);
		});
		sc.document.addEventListener(Mojo.Event.stageDeactivate, function() {
			sc.useSceneTransitions(false);
		});
		if (PalmSystem && PalmSystem.isMinimal && !(this.otasp)) {
			sc.activate();
		}
		callback(sc);
				
	}.bind(this);
	
	appControl.createStageWithCallback({
		lightweight: true,
		name: "PhoneApp"
	}, f);
	
};

AppAssistant.prototype.onShow = function(params) {
	QDLogger.log( "onShow", params);
	this.handleLaunch(Mojo.convertLaunchParams(params));
};

AppAssistant.prototype.onHide = function(sc){
	QDLogger.log( "onHide");
	this.hidden = true;
	// if we're on calls, kill them
	if (this.telephonyEventListener.callExists()) {
		this.telephonyEventListener.disconnectAllCalls();
	} else {
	// otherwise, if dialpad or one of its children is on top
	// pop to it to reset it 
		if (this.topScene() == "dialpad") {
			sc.popScenesTo("dialpad");
			sc.delegateToSceneAssistant("resetOnHide")
		} 
	}
};

// shows the contact picker. always pushes on top of existing scenes
AppAssistant.prototype.showPicker = function(firstCharacter){
	// make a stage if we don't have one
	QDLogger.log( "showPicker");

	var sceneName = "picker";
	var stageController = Mojo.Controller.getAppController().getStageController("PhoneApp");

	// push the scene
	var pushPickerScene = function(sc) {
		QDLogger.log( "showPicker", "pushing: " , sceneName);
		this.sceneStack.push(sceneName);
		sc.pushScene(sceneName, firstCharacter);
	}.bind(this);
   
	if (stageController === undefined) {
		QDLogger.log( "showPicker", "creating stage:" , this.stageName ," because " , stageController);
	 	this.createPhoneStage(pushPickerScene);
		return;
	} else if (this.topScene() !== sceneName){
		stageController.activate();
		pushPickerScene(stageController);
	} else {
		QDLogger.log( "showPicker", "we think picker is on top");
		stageController.activate();
	}
};

// shows the call log.  always pushes on top of existing scenes
AppAssistant.prototype.showCallLog = function(type, keepHidden){
	// make a stage if we don't have one
	QDLogger.log( "showCallLog");
	
	var sceneName = "calllog";
	var stageController = Mojo.Controller.getAppController().getStageController("PhoneApp");

	// push the scene
	var pushLogScene = function(sc) {
		QDLogger.log( "showCallLog", "pushing: " , sceneName);
				
		this.sceneStack.push(sceneName);
		
		// insert voicemail count into template before transition occurs
		var voicemailCount = this.telephonyEventListener.lastVoicemail, voicemailCountDisplayClass = 'hidden';
		if (voicemailCount > 0) {
			if (voicemailCount == this.announcer.kUnknownVoicemailCount) {
				voicemailCount = Messages.newVoicemailUnknownCount;
			}
			voicemailCountDisplayClass = '';
		}
		
		sc.pushScene({
			name: sceneName,
			transition: Mojo.Transition.crossFade,
			templateModel: {
				voicemailCount: voicemailCount,
				voicemailCountDisplayClass: voicemailCountDisplayClass
			}
		}, type);
	}.bind(this);
   
	if (stageController === undefined) {
		this.createPhoneStage(pushLogScene);
	} else {
		// FIXME keepalive
		if (!keepHidden)
			this.showPhoneStage(stageController.window);
		if (this.topScene() !== sceneName) {
			stageController.activate();
			pushLogScene(stageController);
		} else {
			QDLogger.log( "showCallLog", "we think call log is on top");
			stageController.activate();
			if ( type == "missed" ) {
				stageController.delegateToSceneAssistant("onMissedCallsTap", true);
			}
		}
	}
};

AppAssistant.prototype.showProvisioning = function(params){
	// make a stage if we don't have one
	QDLogger.log( "showProvisioning");

	var sceneName = "provisioning";
	var stageController = Mojo.Controller.getAppController().getStageController("PhoneApp");

	// push the scene
	var pushProvisioningScene = function(sc) {
		QDLogger.log( "showProvisioning", "pushing: " , sceneName);
		sc.pushScene(sceneName, params);
	};
   
	if (stageController === undefined) {
		QDLogger.log( "showProvisioning", "creating stage:" , this.stageName ," because " , stageController);
	 	this.createPhoneStage(pushProvisioningScene);
		return;
	} else {
		var activeScene = stageController.activeScene();
		stageController.activate();
		// if the provisioning is already on top, then just return
		if (activeScene && activeScene.sceneName == sceneName) {
			QDLogger.log( "showProvisioning", "found");
			this.provisioningAsist = new ProvisioningAssistant(this);
			var msgElement = stageController.window.document.getElementById('message');
			msgElement.innerHTML = params;
			stageController.activate();
			return;
		} else {
			QDLogger.log( "showProvisioning", "will push");
			// otherwise push it
			pushProvisioningScene(stageController);
			
		}
	}
};

// shows the dialpad. pops existing scenes to get to it (except for active call.)
AppAssistant.prototype.showDialpad = function(dialParams, keepHidden){
	// make a stage if we don't have one
	QDLogger.log( "showDialpad");
	
	var sceneName = "dialpad";
	var stageController = Mojo.Controller.getAppController().getStageController("PhoneApp");
	var stageProxy = Mojo.Controller.getAppController().getStageProxy("PhoneApp");
	
	// push the scene
	var pushDialpadScene = function(sc) {
				
		QDLogger.log( "showDialpad", "pushing: " , sceneName);
		this.sceneStack.push(sceneName);
		sc.pushScene({
			"name": sceneName,
			automaticFocusAdvance: false,
			transition: Mojo.Transition.crossFade
		}, dialParams);
		
	}.bind(this);
   
	if (stageController === undefined && stageProxy === undefined) {
		QDLogger.log( "showDialpad", "creating stage:" , this.stageName ," because " , stageController);
	 	this.createPhoneStage(pushDialpadScene);
		return;
	} else if (stageController !== undefined) {
		if (!keepHidden)
			this.showPhoneStage(stageController.window); 
		// if no scenes, push dialpad
		if (this.sceneStack.length == 0) {
			QDLogger.log( "showDialpad", "will push");
			pushDialpadScene(stageController);
		} else {
			var sceneIdx = this.sceneStack.indexOf(sceneName);
			if (sceneIdx == this.sceneStack.length-1) {
				QDLogger.log( "showDialpad", "already on top");
				stageController.delegateToSceneAssistant("handleSceneArgs",dialParams)
				return;
			// if there's a dialpad, pop to it
			} else if (sceneIdx !== -1) {
				QDLogger.log( "showDialpad", "popping to");
				this.sceneStack = this.sceneStack.slice(0,sceneIdx+1);
				stageController.popScenesTo(sceneName, dialParams);
			} else {
				var activeCallIdx = this.sceneStack.indexOf("activecall");
				// if there's an active call pop to it...
				if (activeCallIdx !== -1) {
					QDLogger.log( "showDialpad", "popping to active call");
					this.showActiveCall();
				} else {
				// otherwise blow away all scenes
					this.sceneStack = [];
					QDLogger.log( "showDialpad", "popping all");
					stageController.popScenesTo();
				}
				// ...then push dialpad scene
				pushDialpadScene(stageController);
			}
		} 
	} 
};

AppAssistant.prototype.focusStage = function(){
	// if launched in OTASP mode, don't focus phone app
	if (this.otasp) {
		return;
	}
	
	var stageController = Mojo.Controller.getAppController().getStageController("PhoneApp");
	if (stageController)
		stageController.activate();	
}

// shows the active call scene.  pops all existing scenes to get to it
AppAssistant.prototype.showActiveCall = function(pauseWaitDigits){
	var sceneName = "activecall";
	QDLogger.log( "showActiveCall");
	
	if (!(this.telephonyEventListener.callExists())) {
		QDLogger.error("showActiveCall: attempted to show but no call exists"); 
		return;
	} 
	
	var stageController = Mojo.Controller.getAppController().getStageController("PhoneApp");
	var stageProxy = Mojo.Controller.getAppController().getStageProxy(this.stageName);
	
	var pushActivecallScene = function(sc) {		
		QDLogger.log( "showActiveCall", "pushing: " , sceneName, pauseWaitDigits);
		this.sceneStack.push(sceneName);
		sc.pushScene({
			"name": sceneName,
			transition: Mojo.Transition.crossFade
		}, pauseWaitDigits);
		
	}.bind(this);
	

	// if in screenlock mode, show that version of the active call too
	if (this.isScreenLockedAndSecured()) {
		this.showLockScreenActiveCall();	
	} 
	
	// if in emergency mode, show that version of the active call
	if (this.emergencyMode) {
		this.showEmergencyActiveCall();
	}
	
	// don't bother showing active call if it's been aborted
	if(this.telephonyEventListener.callAborted()) {
		QDLogger.log( "showActiveCall", "call aborted: not showing");
		return;
	}
	
	// if we don't have a stage, make one then push scene
    if (stageController === undefined && stageProxy === undefined) {
		QDLogger.log( "showActiveCall", "creating stage:" , this.stageName);
		this.createPhoneStage(pushActivecallScene);
		return;
	} else if (stageController !== undefined) {
		// if no scenes, push activecall
		if (this.sceneStack.length == 0) {
			QDLogger.log( "showActiveCall", "will push");
			pushActivecallScene(stageController);
		} else {
			var sceneIdx = this.sceneStack.indexOf(sceneName);
			if (sceneIdx == this.sceneStack.length - 1) {
				QDLogger.log( "showActiveCall", "already on top");
				return;
			// if there's an active call, pop to it
			} else if (sceneIdx !== -1) {
				QDLogger.log( "showActiveCall", "popping to");
				this.sceneStack = this.sceneStack.slice(0,sceneIdx+1);
				stageController.popScenesTo(sceneName);
			// otherwise pop everthing and push new one
			} else {
				QDLogger.log( "showActiveCall", "popping all & pushing");
				if(this.sceneStack[this.sceneStack.length-1] == 'calllog')
					this.backToCalllog = true;
				this.sceneStack = [];
				stageController.popScenesTo();
				pushActivecallScene(stageController);
			}
			this.showPhoneStage(stageController.window);
		}
		if (PalmSystem && PalmSystem.isMinimal) {
			this.focusStage();
		}
	} 
};

// closes the phone stage and resets scene state
AppAssistant.prototype.closePhoneStage = function() {
	QDLogger.log( "closePhoneStage");
	
	var stageController = this.appController.getStageController(this.stageName);
	if (stageController !== undefined) {
		// use keepalive version if available
		if (stageController.window.PalmSystem.hide !== undefined && !(PalmSystem.isMinimal)) {
			this.hidePhoneStage(stageController.window);
			return;	
		} 
		this.appController.closeStage("PhoneApp");
	}
	
	this.sceneStack = [];
	
	if (this.firstuse && !(this.emergencyMode))
		this.announcer.remoteDialogScene = undefined;
}

AppAssistant.prototype.hidePhoneStage = function(win) {
	QDLogger.log("AppAssistant#hidePhoneStage");
	this.appController.getStageController(this.stageName).useSceneTransitions(false);
	win.PalmSystem.hide();
	this.showDialpad(null, true /* keep hidden */);
	this.hidden = true;
}

AppAssistant.prototype.showPhoneStage = function(win) {
	QDLogger.log( "showPhoneStage");
	if(this.hidden) {
		this.appController.getStageController(this.stageName).useSceneTransitions(true);
		QDLogger.log( "showPhoneStage", "showing!");
		this.hidden = false;
		win.PalmSystem.show();
		//this.focusStage();
	}
}

// removes all scenes and shows the dialpad
// TODO: really should deal with other scene being stacked on dialpad and not disturb
// them
AppAssistant.prototype.popActiveCall = function(params) {
	QDLogger.log( "popActiveCall");
	
	if (params) {
		if (params.lock) {
			this.popLockScreenActiveCall();
			return;
		} else if (params.emergency) {
			this.popEmergencyActiveCall();
			return;
		}
	}
	
	var stageController = this.appController.getStageController(this.stageName);
	if (stageController !== undefined) {
		this.sceneStack = [];
		stageController.popScenesTo();
		if (this.backToCalllog) {
			if(this.calllogType == "missed")
				this.showCallLog("missed", true);
			else
				this.showCallLog("", true);
		}
		else 
			this.showDialpad(null, true);
	} else {
		QDLogger.log( "popActiveCall", "undefined stage");
	}
	this.backToCalllog = false;
}

// manages tearing down the fake active call screen on a dialing error.
// if the active call screen hasn't been created yet, prevents it from happening.
// if it has, pops it.
// in both cases, returns to dialpad.
AppAssistant.prototype.closeActiveCallOnError = function() {
	QDLogger.error( "closeActiveCallOnError");
	var stageController = this.appController.getStageController(this.stageName);
	
	if (this.hasScene("activecall") && stageController !== undefined) {
		QDLogger.log( "closeActiveCallOnError", "popping active call");
		this.sceneStack = [];
		stageController.popScenesTo();
	} else {
		QDLogger.error( "closeActiveCallOnError", "aborting next active call stage open");
		this.telephonyEventListener.abortActiveCall = true;
	}
	//Do not launch Dialpad on dial failure during FirstUse run.
	if(!PalmSystem.isMinimal)
		this.showDialpad();
};

AppAssistant.prototype.mockIncomingCall = function() {
	this.showMockIncomingThumbnail = !(this.showMockIncomingThumbnail);
	var contact = {name: "Zooey Glass"};
	if (this.showMockIncomingThumbnail) {
		contact.id = 4242
		contact.pictureLoc = "images/amy.jpg";
		contact.pictureObj = new Image();
		contact.pictureObj.src = "images/amy.jpg"
		contact.pictureFormat = "portrait"
	}
	this.announcer.announceIncoming(1, "4158675309", contact, true, false);
};

function StageAssistant(stageController){
    this.stageController = stageController;
    QDLogger.log( "StageAssistant", document.baseURI, Palm);
};

// makes lockscreen stage and attaches listeners
AppAssistant.prototype.createLockStage = function() {
	var setupListeners = function(stageController) {
		stageController.document.addEventListener(Mojo.Event.stageActivate, this.onLockScreenActivate);
		stageController.document.addEventListener(Mojo.Event.stageDeactivate, this.onLockScreenDeactivate);
		stageController.document.body.className = "firstuse";
		if (stageController.window) {
			stageController.window.PalmSystem.stageReady();
		}
	}.bind(this);
	
	var stageProxy = Mojo.Controller.getAppController().getStageProxy("PhoneAppPincode");

	
	// if we don't have a stage, make one then push scene
    if (stageProxy === undefined) {
		QDLogger.log( "showLockScreen", "creating stage: PhoneAppPincode");
		var appControl = Mojo.Controller.getAppController();
		appControl.createStageWithCallback({
			lightweight: true,
			name: "PhoneAppPincode"
		}, setupListeners, 'pin');
	} else if (stageController !== undefined) {//other app alread using pincode scene, could be a problem here, just push scene on the top?
		QDLogger.error("createLockScreen: attempted to create lock screen twice");
	} 
};

// when activated, push lockscreen scene unless there's a call
AppAssistant.prototype.onLockScreenActivate = function() {
	if (!(this.telephonyEventListener.incomingCallExists()) 
		&& !(this.telephonyEventListener.callExists())) {
		this.showLockScreen();
	} else {
		// confirm that we actually get an active call in case a disconnect
		// arrives late
		//
		// check for active call after 3 seconds.  if it doesn't exist, push
		// lock screen.  
		this.waitForActiveCallTimeout = setTimeout(function(){
			if (!(this.telephonyEventListener.incomingCallExists()) 
				&& !(this.telephonyEventListener.callExists())) {
				this.showLockScreen();
			}
		}.bind(this), 3000);
		
	}
};

// when deactivated, pop all scenes
AppAssistant.prototype.onLockScreenDeactivate = function() {
	this.lockStageHasActiveCall = false;
	var stageController = Mojo.Controller.getAppController().getStageController("PhoneAppPincode");
	clearTimeout(this.waitForActiveCallTimeout);
	this.waitForActiveCallTimeout = undefined;
	if (stageController) {
		stageController.popScenesTo();
		this.lockSceneStack = [];
	} else {
		QDLogger.error("onLockScreenDeactivate: can't pop, no lock screen");
	}
};

// 
AppAssistant.prototype.showLockScreen = function() {
	var sceneName = "pin";
	QDLogger.log( "showLockScreen");
	var stageController = Mojo.Controller.getAppController().getStageController("PhoneAppPincode");
	
    if (stageController !== undefined) {
		if (this.topScene() && this.topScene() == sceneName) {
			stageController.popScenesTo("pin");
		}
		else {
			this.lockSceneStack.push(sceneName);
			stageController.pushScene({
				"name": sceneName,
				transition: Mojo.Transition.crossFade,
				automaticFocusAdvance: false // so we can capture the 'return' key using Mojo.Event.keypress on the sceneElement
			});
		}
	}
};

// pops all the scenes off the pin stage then pushes the active call
AppAssistant.prototype.showLockScreenActiveCall = function() {
	var stageController = Mojo.Controller.getAppController().getStageController("PhoneAppPincode");
	clearTimeout(this.waitForActiveCallTimeout);
	this.waitForActiveCallTimeout = undefined;
	if (stageController) {
		if (this.lockStageHasActiveCall) {
			stageController.popScenesTo("activecall");
			//Remove pin scene from the sceneStack. NOV-69092
			var scIdx = this.lockSceneStack.indexOf("pin");
			if(scIdx != -1)
				this.lockSceneStack.splice(scIdx,1);
		} else {
			this.lockStageHasActiveCall = true;
			stageController.pushScene({
				"name": "activecall",
				transition: Mojo.Transition.crossFade
			}, 
			{
				locked: true
			});
			this.lockSceneStack = ['activecall'];
		}
	} else {
			QDLogger.error("onLockScreenDeactivate: can't pop, no lock screen");
	}
};

// pops all the scenes off the pin stage then pushes the pincode stage
AppAssistant.prototype.popLockScreenActiveCall = function() {
	QDLogger.log( "popLockScreenActiveCall");
	var stageController = Mojo.Controller.getAppController().getStageController("PhoneAppPincode");
	
	if (stageController) {
		stageController.popScenesTo();
		// TODO FIXME USE SWAPSCENE
		this.lockStageHasActiveCall = false;
		this.lockSceneStack = [];
		this.showLockScreen();
	}
};

// screen lock on and requires a pin or password
AppAssistant.prototype.isScreenLockedAndSecured = function() {
	return this.screenLockSecured && this.screenLocked;
}

// opens emergency stage.  pushes callback scene, and activecall if there's one
AppAssistant.prototype.showEmergencyStage = function() {
	QDLogger.log( "showEmergencyStage");
	var stageController = Mojo.Controller.getAppController().getStageController("emergency");
	var stageProxy = Mojo.Controller.getAppController().getStageProxy("emergency");
	
	var pushEmergencyScenes = function(sc) {
		sc.pushScene({
			"name": "emergencymode",
			transition: Mojo.Transition.crossFade
		});
		if (this.telephonyEventListener.callExists()) {
			sc.pushScene({
				"name": "activecall",
				transition: Mojo.Transition.crossFade
			}, {
				"emergency":true
			});
			this.emergencyCallActive = true;
		}
	}.bind(this);
	
	// make stage then push scenes
    if (stageController === undefined && stageProxy === undefined) {
		QDLogger.log( "showEmergencyStage", "creating stage");

		var appControl = Mojo.Controller.getAppController();
		var stageType = (PalmSystem.isMinimal ? 'card' : 'emergency')
		appControl.createStageWithCallback({
			lightweight: true,
			name: "emergency"
		}, pushEmergencyScenes, stageType);
	} else {
		QDLogger.log( "showEmergencyStage", "stage already exists");
	}
}

AppAssistant.prototype.closeEmergencyStage = function() {
	var stageController = this.appController.getStageController("emergency");
	
	if (stageController !== undefined) {
		this.appController.closeStage("emergency");
		this.emergencyCallActive = false;
		
		this.announcer.remoteDialogScene = undefined;
	} 
}

// show the emergency callback window; create a new stage if necessary
AppAssistant.prototype.showEmergencyCallback = function() {
	var sceneName = "emergencymode";
	QDLogger.log( "showEmergencyCallback");
	var stageController = Mojo.Controller.getAppController().getStageController("emergency");
	var stageProxy = Mojo.Controller.getAppController().getStageProxy("emergency");
	
	// if we don't have a stage, make one then push scene
    if (stageController === undefined && stageProxy === undefined) {
		this.showEmergencyStage();	
	// remove all scenes; emergencymode scene should be at the bottom
	} else if (stageController !== undefined) {
		stageController.popScenesTo("emergencymode");
	} 
}

// pushes new emergency active call scene or pops to existing one
AppAssistant.prototype.showEmergencyActiveCall = function() {
	var sceneName = "activecall";
	QDLogger.log( "showEmergencyActiveCall");
	var stageController = Mojo.Controller.getAppController().getStageController("emergency");
	var stageProxy = Mojo.Controller.getAppController().getStageProxy("emergency");
	
	var pushEmergencyActiveCall = function(sc) { 
		QDLogger.log( "showEmergencyCallback", "pushing: " , sceneName);
		sc.pushScene({
			"name": sceneName,
			transition: Mojo.Transition.crossFade
		}, {"emergency":true});
		this.emergencyCallActive = true;
	}.bind(this);
	
	// if we don't have a stage, make one then push scene
    if (stageController === undefined && stageProxy === undefined) {
		this.showEmergencyStage();	
	// push this one, if it's not already up
	} else if (stageController !== undefined) {
		if (this.emergencyCallActive) {
			stageController.popScenesTo(sceneName);
		} else {
			this.emergencyCallActive = true;
			stageController.pushScene({
				"name": sceneName,
				transition: Mojo.Transition.crossFade
			}, {"emergency":true});
		}
	} 
}

// remove emergency active call; restore callback scene
AppAssistant.prototype.popEmergencyActiveCall = function() {
	this.showEmergencyCallback();
	this.emergencyCallActive = false;
}

// show dialpad for emergency call and screenlock
AppAssistant.prototype.showLimitedDialpad = function(sc, dialParams) {
	dialParams.limited = true;
		
	sc.pushScene({
		"name": "dialpad",
		automaticFocusAdvance: false,
		transition: Mojo.Transition.crossFade
	}, dialParams);
}

AppAssistant.prototype.loadSettings = function() {
	this.stickyDialpad = this.getStickyDialpad();
	this.numberMaskEnabled = PhoneNumberFormatter.prototype.masked = this.getNumberMaskEnabled();
	
	TelephonyCommands.subscribePreference("phoneAppHideCallDuration", this.onHideCallDurationPreferenceReturn.bind(this));
	TelephonyCommands.subscribePreference("PhoneAppRebootOnRadioSettingsChanged", this.onRebootOnRadioSettingsChangedReturn.bind(this));
	TelephonyCommands.subscribePreference("otaspNumber", this.onOtaspNumberReturn.bind(this));
	TelephonyCommands.subscribePreference("otaspFailedCustomerServiceNumber", this.onOtaspFailedCustomerServiceNumber.bind(this));
	TelephonyCommands.subscribePreference("phoneInternationalDialingActive", this.onInternationalDialingReturn.bind(this));
	TelephonyCommands.subscribePreference("phoneInternationalDialingRegionId", this.onInternationalDialingRegionReturn.bind(this));
	TelephonyCommands.subscribePreference("phoneIncomingCallOnCallSound", this.onIncomingCallOnCallSound.bind(this));
	
	TelephonyCommands.subscribePreference("PhoneAppGSMNorthAmericanSettings", function(value) {
		this.phoneGSMNorthAmericanSettings = !!value;
	}.bind(this));
	
	TelephonyCommands.subscribePreference("PhoneAppEmergencyModeExpiration", function(value) {
		this.emergencyModeExpirationMs = parseInt(value,10) || 0;
	}.bind(this));
	
	TelephonyCommands.subscribePreference("PhoneAppHideEmergencyNumbersFromCallLog", function(value) {
		this.hideEmergencyNumbersFromCallLog = !!value;
	}.bind(this));
	
	TelephonyCommands.subscribePreference("PhoneAppUseCarrierVerificationMMSCURL", function(value) {
		this.useCarrierVerificationMMSCURL = !!value;
	}.bind(this));
	
}

// gets phone number mask enabled; defaults to false
AppAssistant.prototype.getNumberMaskEnabled = function() {
	var numberMaskCookie = new Mojo.Model.Cookie("numbermask");
	if (numberMaskCookie) {
		var value = numberMaskCookie.get();
		  // 0 is false; everything else is true  
          return value === "1"       
	} else {
		return false;
	}
};

// set to true if  number should be masked with '(xxx) 555-01xx'
AppAssistant.prototype.setNumberMaskEnabled = function(enabled) {
	var value = (enabled === true ? "1" : "0");
	var numberMaskCookie = new Mojo.Model.Cookie("numbermask");
	if (numberMaskCookie) {
		numberMaskCookie.put(value);
	}
	this.numberMaskEnabled = PhoneNumberFormatter.prototype.masked = enabled;
};

// get dialpad stickymode status; defaults to true
AppAssistant.prototype.getStickyDialpad = function() {
	var stickyDialpadCookie = new Mojo.Model.Cookie("stickydialpad");
	if (stickyDialpadCookie) {
		var value = stickyDialpadCookie.get();
		  // 0 is false; everything else is true  
          return !(value === "0")       
	} else {
		return true;
	}
};

// sets dialpad stickymode status
AppAssistant.prototype.setStickyDialpad = function(enabled) {
	var value = (enabled === true ? "1" : "0");
	var stickyDialpadCookie = new Mojo.Model.Cookie("stickydialpad");
	if (stickyDialpadCookie) {
		stickyDialpadCookie.put(value);
	}
	this.stickyDialpad = enabled;
};

// should we hide call times in call log? defaults to false
AppAssistant.prototype.onHideCallDurationPreferenceReturn = function(value) {
	QDLogger.log( "onHideCallDurationPreferenceReturn", value);
	this.hideCallLogCallDuration = !!value;
};

// NOTE THAT THIS SETTING HAS AN INITIAL CAP UNLIKE EVERY OTHER SETTING
AppAssistant.prototype.onRebootOnRadioSettingsChangedReturn = function(value) {
	this.rebootOnRadioSettingsChanged = value;
};

AppAssistant.prototype.onOtaspNumberReturn = function(value) {
	QDLogger.log( "otaspNumber", value);
	this.otaspNumber = value;
};

AppAssistant.prototype.onOtaspFailedCustomerServiceNumber = function(value) {
	QDLogger.log( "onOtaspFailedCustomerServiceNumber", value);
	if (value && value.length > 1) {
		this.otaspCustomerServiceNumber = value;
		Messages.otaspNotifications["programmingfailed"] = Messages.otaspFailureBanner.interpolate({"value": value});
		Messages.otaspNotificationsDashboardTranslations["programmingfailed"][1] = Messages.otaspFailureWithInfoBody.interpolate({"value": value});
	}
};


AppAssistant.prototype.onInternationalDialingReturn = function(value) {
	this.internationalDialingEnabled = value;
};

AppAssistant.prototype.onIncomingCallOnCallSound = function(value) {
	AnnouncerAssistant.kDefaultCallOnCallSound = value;
}

AppAssistant.prototype.onInternationalDialingRegionReturn = function(value) {
	this.internationalDialingRegionId = value;
	var settings = InternationalDialingSettings.get(this.internationalDialingRegionId);
	if (settings) {
		// if the selected country is a nanp country, don't bother.
		if (!settings.nanp) {
			this.internationalDialingPrefix = InternationalDialingSettings.get(this.internationalDialingRegionId).idd
		} else {
			this.internationalDialingPrefix = "";
		}
	}
};

//quick dial (prefix for 4-7 numbers)
AppAssistant.prototype.registerPhoneprefsForQuickdial = function(){
	var keys=[];
	for (var i = 4; i < 8; i++) {
		keys[i]=i+"DigitNumber";		
	}
	this.quickdialRequest = new Mojo.Service.Request("palm://com.palm.systemservice", {
		method: 'getPreferences',
		parameters: {
			"subscribe":true,
			'keys': keys
		},
		onSuccess: (function(keys,response){
			if (response) {
				for (var i = 4; i < 8; i++) {
					var key=keys[i];
					if (response[key]) {
						this.quickDial[i] = response[key];
					}
					else if(response[key] == false)
					{
						this.quickDial[i] = undefined
					}
				}
			}
		}).bind(this,keys),
	});
};   
AppAssistant.prototype.getQuickdialPrefix = function(str){
	for (var i=0; i<str.length; i++){
		if (str.slice(i, i+1) > '9' || str.slice(i, i+1) < '0') {
			return undefined
		}
	}
	return this.quickDial[str.length];
};

AppAssistant.prototype.setupShutdown = function() {
	// register for shutdown signal
	// trigger shutdown: novacom run file:///usr/bin/luna-send -- -n 1 palm://com.palm.power/shutdown/machineOff '{"reason":"Shutdown"}'
	this.shutdownSubscriptionRequest = new Mojo.Service.Request('palm://com.palm.bus/signal/', {
	    method: 'addmatch',
	    parameters: {"category":"/shutdown","method":"shutdownApplications"},
	    onSuccess: this.handleShutdown.bind(this),
	    onFailure: function(e) {
	        Mojo.Log.error("shutdownApplications error: %j",e);
	    }
	});
	
	// register with shutdown system
	this.shutdownRegistrationRequest = new Mojo.Service.Request('palm://com.palm.power/shutdown', {
	    method: 'shutdownApplicationsRegister',
	    parameters: {"clientName":"com.palm.app.phone"},
	    onSuccess: function(response) {
			this.shutdownRegistrationId = response.clientId;
		}.bind(this),
	    onFailure: function(e) {
	        Mojo.Log.error("shutdownApplicationsRegister error: %j",e);
	    }
	});
};

// called when a shutdown signal is received. Ensures calls are disconnected before acknowledging.
AppAssistant.prototype.handleShutdown = function() {
	// ignore first response after registration
	if ( ! this.shutdownRegistrationId ) {
		return;
	}
	
	if ( this.telephonyEventListener.callExists() ) {
		// add a watcher to know when all calls are disconnected
		this.telephonyEventListener.addCallStateListener({
			updateCalls: function(callState) {
				if ( callState.lines.length == 0 ) {
					this.acknowledgeShutdown();
				}
			}.bind(this)
		});
		
		// disconnect all calls
		this.telephonyEventListener.disconnectAllCalls();
	} else {
		this.acknowledgeShutdown();
	}
};

// acknowledges a shutdown by sending a message that it is OK to proceed
AppAssistant.prototype.acknowledgeShutdown = function(callState) {
	new Mojo.Service.Request('palm://com.palm.power/shutdown', {
	    method: 'shutdownApplicationsAck',
	    parameters: {"clientId": this.shutdownRegistrationId},
	    onFailure: function(e) {
	        Mojo.Log.error("shutdownApplicationsAck error: %j",e);
	    }
	});
};

AppAssistant.prototype.configureLocale = function configureLocale(){
	// TODO: disables phone number formatting in non-NA locales 
	var region = Mojo.Locale.getCurrentFormatRegion() || '';
	// getCurrentFormatRegion returns language_region (and on some frameworks, just region) 
	// so look at the last 2 characters 
	switch (region.substr(-2)) {
		case 'us':
		case 'ca':
			this.nanpRegion = true;
			return;
		default:
			this.nanpRegion = false;
			PhoneNumberFormatter.prototype.disabled = true;
	} 
};

// Primes the call log cache. Can only be called once.
AppAssistant.prototype.primeCallLog = function() {
	var hasBeenPrimed = false;
	
	return function() {
		// only prime the first time
		if ( ! hasBeenPrimed ) {
			hasBeenPrimed = true;
			
			CallLogList.logQuery(0, 200, "");
			CallLogList.logQuery(0, 200, "missed");
		}
	};
}();
